package org.lc.cmd.connect.impl.ws;

import lombok.extern.slf4j.Slf4j;
import org.lc.cmd.SysReqHandler;
import org.lc.cmd.connect.CmdServer;
import org.lc.cmd.connect.RequestHandler;
import org.lc.cmd.connect.ServerContext;
import org.lc.cmd.connect.impl.util.JsonUtil;
import org.lc.cmd.impl.DefaultSysReqHandlerChain;
import org.lc.cmd.impl.sysreq.BaseReceivedSysReq;
import org.lc.cmd.model.CmdRequestData;
import org.lc.cmd.model.RequestFinishData;
import org.lc.cmd.model.StyledString;
import org.lc.cmd.model.SysReqData;
import org.lc.cmd.model.WsData;
import org.lc.cmd.util.ContextUtil;
import org.lc.cmd.util.Lazy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@Configuration
@EnableWebSocket
public class WsServer implements CmdServer, WebSocketConfigurer {
    @Autowired
    private WsEndpoint wsEndpoint;

    private static final Map<String, WsSession> map = new ConcurrentHashMap<>();

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(wsEndpoint, "/cmd/ws/{id}").setAllowedOrigins("*");
    }

    @Override
    public void removeSession(String sessionId) {
        map.entrySet().removeIf(entry -> entry.getValue().getId().equals(sessionId));
    }

    @Component
    public static class WsEndpoint extends TextWebSocketHandler {
        @Autowired
        private List<SysReqHandler> handlers;

        @Autowired
        private RequestHandler requestHandler;

        private final Lazy<WsServer> server = new Lazy<>(() -> ContextUtil.getBean(WsServer.class));

        @Override
        public void afterConnectionEstablished(WebSocketSession session) {
            log.info("open");
            String path = Objects.requireNonNull(session.getUri()).getPath();
            String clientSessionId = path.substring(path.lastIndexOf('/') + 1);
            WsSession wsSession = new WsSession(clientSessionId, session, server.get());
            WsSession oldSession = map.put(session.getId(), wsSession);
            if (oldSession != null) {
                try {
                    oldSession.close();
                } catch (IOException e) {
                    log.info("failed to close old Session of {}", clientSessionId);
                }
            }
        }

        @Override
        public void handleTextMessage(WebSocketSession session, TextMessage message) {
            WsData data = JsonUtil.parse(message.getPayload(), WsData.class);
            switch (data.getType()) {
                case "request":
                    log.info("request: {}", data.getData());
                    CmdRequestData cmdData = JsonUtil.parse(data.getData(), CmdRequestData.class);
                    ServerContext context = createContext(cmdData);
                    requestHandler.handle(context);
                    break;
                case "sysReq":
                    SysReqData sysReqData = JsonUtil.parse(data.getData(), SysReqData.class);
                    BaseReceivedSysReq req = new BaseReceivedSysReq(map.get(session.getId()), true, sysReqData);
                    new DefaultSysReqHandlerChain(handlers).next(req);
                    break;
                case "request-confirm":

                default:
                    log.warn("unknown type: {}, data: {}", data.getType(), data.getData());
            }
        }

        @Override
        public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
            log.info("ws close {}", status);
        }

        private ServerContext createContext(CmdRequestData data) {
            // TODO
            return null;
        }
    }
}
